#ifndef _INCLUDE_onet_BITSTREAM_
#define _INCLUDE_onet_BITSTREAM_

/*!
    Copright: Kai "DrHalan" Mast - www.freakybytes.org

    This file is part of OpenNetwork. See OpenNetwork.h for license information.
*/

#include <OpenNetwork/OpenNetwork.h>

#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>

namespace onet
{

class BitStream
{
    public:
        BitStream()
        {
            bitIndex = 0;
        }

        BitStream(const u32 packetSize)
        {
            bitIndex = 0;
            buffer.resize(packetSize, 0);
        }

        BitStream(const c8* pCharBuffer, const u32 length)
        {
            bitIndex = 0;

            //Load in the buffer to be used
            buffer.resize(length);
            memcpy(&buffer[0], pCharBuffer, length);
        }

        //! WRITE FUNCTIONS
        template<class T> void write(const T value, const bool resize = true)
        {
            if(resize)
                buffer.resize((bitIndex+(sizeof(T)*8)+7)/8);

            for(u8 copyBits = 0; copyBits < (u8) (sizeof(T)*8); ++copyBits)
            {
                buffer[(bitIndex+copyBits)/8] |= ((value >> ((sizeof(T)*8-1)-copyBits)) & 0x1)<<(7-(bitIndex+copyBits)%8);
            }

            bitIndex += sizeof(T)*8;
        }

        template<class T> void writeArray(const std::vector<T> & value, const bool resize = true)
        {
            u16 size = u16(value.size());
            this->write<u16>(size, resize);

            if(resize)
                buffer.resize((bitIndex+(sizeof(T)*8*size)+7)/8);

            for(u32 u = 0; u < size; u++)
            {
                this->write(value[u], false);
            }
        }

        //! Nearly the same as writeArray but you've to give the size manually
        //! Also it takes a pointe to an T-Array
        //! Mainly used by the UDP-Class itself
        template<class T> void writeRawArray(const T* value, const u32 size, const bool resize = true)
        {
            if(resize)
                buffer.resize((bitIndex+(size*8)+7)/8);

            for(u32 u = 0; u < size; u++)
            {
                this->write<T>(value[u], false);
            }
        }


        //! READ FUNCTIONS
        template<class T> T read(const bool increment = true)
        {
            T value = 0;

            for(c8 copyBits = 0; copyBits < (c8) (sizeof(T)*8); ++copyBits)
            {
                value <<= 1;
                value |= (buffer[(bitIndex+copyBits)/8]>>(7-(bitIndex+copyBits)%8)) & 0x1;
            }

            if(increment)
                bitIndex += sizeof(T)*8;

            return value;
        }

        template<class T> std::vector<T> readArray(const bool increment = true)
        {
            u16 size = read<u16>(true);
            std::vector<T> value;
            value.resize(size);

            for(u16 cycleArray = 0; cycleArray < size; ++cycleArray)
            {
                this->read<T>(true);
            }

            if(!increment)
                bitIndex -= sizeof(T)*8*size+8;

            return value;
        }

        //! Nearly the same as ready array but you've to give the size manually
        //! Also it returns a pointer to a T-Array
        //! Mainly used by the UDP-Class itself
        template<class T> T* readRawArray(const u32 size, const bool increment = true)
        {
            T* value = new T[size];

            for(u32 u = 0; u < size; u++)
            {
                value[u] = this->read<T>(true);
            }

            if(!increment)
                bitIndex -= size*8;

            return value;
        }

        //! Copy a Raw Array directly to another BitStream (e.g. used in TCP port)
        template<class T> void copyRawArray(BitStream* other, const u32 size, const bool increment = true, const bool resize = true)
        {
            for(u32 u = 0; u < size; u++)
            {
                other->write<c8>(this->read<T>(true), resize);
            }

            if(!increment)
                bitIndex -= size*8;
        }

        //! RESET AND SET BITINDEX
        void resetBitIndex()
        {
            bitIndex = 0;
        }

        void setBitIndex(const s32 index)
        {
            bitIndex = index;
        }

        void setByteIndex(const u32 index)
        {
            bitIndex = 8*index;
        }

        u64 getBitIndex() const
        {
            return bitIndex;
        }

        u32 getByteIndex() const
        {
            return bitIndex/8;
        }

        u32 getLength() const
        {
            return u32(buffer.size());
        }

        c8* getData(const bool copy = false)
        {
            if(copy)
            {
                c8* dataCopy = new c8[this->getLength()];

                memcpy(dataCopy, &buffer[0], this->getLength());

                return dataCopy;
            }
            else
                return &buffer[0];
        }

        std::string getDataAsString()
        {
            return std::string(this->getData(), this->getLength());
        }

        std::vector<c8> getDataAsVector() const
        {
            return buffer;
        }

        void resetBuffer()
        {
            for(std::vector<c8>::iterator bufferItr = buffer.begin(); bufferItr != buffer.end(); ++bufferItr)
            {
                (*bufferItr) = 0;
            }
        }

        //! Some operators for easier acess
        c8 operator[](const u32 pos) const
        {
            if(pos > buffer.size())
                return 0;

            return buffer[pos];
        }

        bool operator==(const BitStream& other) const
        {
            if(other.getLength() != this->getLength())
                return false;

            std::vector<c8> otherData = other.getDataAsVector();

            for(u32 u = 0; u < this->getLength(); u++)
            {
                if(buffer[u] != otherData[u])
                    return false;
            }

            return true;
        }

        void operator+= (BitStream& other)
        {
            u64 oldindex = other.getBitIndex();
            other.setBitIndex(0);

            for(u32 u = 0; u < other.getLength(); u++)
            {
                this->write<c8>(other[u]);
            }

            bitIndex += other.getLength();

            other.setBitIndex(oldindex);
        }

        void operator= (const std::string str)
        {
            buffer.resize(str.length());

            for(u32 u = 0; u < str.length(); u++)
            {
                buffer[u] = str[u];
            }
        }

    private:
        //! The char-array holding the data
        std::vector<c8> buffer;

        //! The reading / writing position
        u64 bitIndex;
};

//! Specialisations
template<> void BitStream::writeArray<f32>(const std::vector<f32> &value, const bool resize);
template<> void BitStream::write<bool>(const bool value, const bool resize);
template<> void BitStream::write<std::string>(std::string value, const bool resize);
template<> void BitStream::write<f32>(const f32 value, const bool resize);
template<> bool BitStream::read<bool>(const bool increment);
template<> f32 BitStream::read<f32>(const bool increment);
template<> std::vector<f32> BitStream::readArray<f32>(const bool increment);
template<> std::string BitStream::read<std::string>(const bool increment);

}

#endif
